newtypeとsmart constructorのmodule
以下の組み合わせのmoduleを設計する
型は公開
値コンストラクタは非公開
公開
unwrapする関数
公開
code:Password.hs
module Password
( Password -- ←これは型
, unPassword
, mkPassword
) where
import Data.ByteString (ByteString)
import qualified Data.ByteString as ByteString
newtype Password = Password ByteString
unPassword :: Password -> ByteString
unPassword (Password password) = password
mkPassword :: ByteString -> Maybe Password
mkPassword pwd
| ByteString.null pwd = Nothing
| otherwise = Just (Password pwd)
Password の値constructorは公開せずに、mkPasswordのみを公開している
Password型を生成するために、mkPasswordを使うように強制できる
mkPasswordでは、validationを行い、不正なデータでPasswordを定義させない
ここでは空文字のPasswordを指定させないようにしている
Passwordの値consturctorを公開していないので、値を取り出すためにunPassword関数が必要
「Usernameは空文字ではいけない」という仕様があって、それをどう表現するか
code:A.hs
newtype Username = Username String
mkUsername :: String -> Maybe Username
mkUsername "" = Nothing
mkUsername s = Just (Username s)
code:B.hs
newtype Username = Username NonEmptyString
Aのほうは、Stringでnewtypeして、空文字validationするために、smart constructorを使っている
Bの方は、そもそも型レベルで空文字を受け付けない
今後の仕様の変わりようも考えると、Aの方が柔軟な気もする
validationに「記号から始まってはいけない」とか「3文字以上」とかの要件が加わる可能性はある
Cとして、NonEmptySringにsmart consturctorを使う、というのもあり得る
これが良いと思うmrsekut.icon
mkする場所やunwrapする場所は、外部との境界値になる
unwrapさせない工夫
unwrapするのではなく、unwrapしたものに適用する関数を渡すようにする
こうではなく
code:before.fs
address |> EmailAddress.value |> printfn "the value is %s"
こうする
code:after.fs
address |> EmailAddress.apply (printfn "the value is %s")
こうすることで、unwrapされた値を持ち回ることを防ぐことができる
unwrapした値を取り出せないので
でもidを適用すれば取り出せるかmrsekut.icon
簡潔でわかりやすい
ここでは、返り値をEither ソレ Stringにしようというふうに書かれている
smart constructorの話ではないけど
String50とString100などを個別に作りつつも互換性をもたせる方法